#ifndef __GMP_H__
#include <gmp.h>
#endif

#ifndef _NTH_LIB_H
#include <nth-lib.h>
#endif

#ifndef _STDINT_H
#include <stdint.h>
#endif

#ifndef _STDIO_H
#include <stdio.h>
#endif

#ifndef _STRING_H
#include <string.h>
#endif

/* Definitions begin here. */

int Clean_Nth_Plugin(
	struct Nth_Plugin * nth_plugin
){
	if (!nth_plugin){
		return NTH_ERROR_NULL_POINTER;
	}

	if (nth_plugin->plugin_name){
		free(nth_plugin->plugin_name);
		nth_plugin->plugin_name = 0;
	}

	if (nth_plugin->plugin_description){
		free(nth_plugin->plugin_description);
		nth_plugin->plugin_description = 0;
	}

	return 0;
}

int Initialize_Nth_Plugin(
	struct Nth_Plugin * nth_plugin
){
	char plugin_name[] = "view";
	char plugin_desc[] = "View a number in standard output";

	if (!nth_plugin){
		return NTH_ERROR_NULL_POINTER;
	}

	nth_plugin->plugin_name = malloc(sizeof(plugin_name));

	if (!nth_plugin->plugin_name){
		return NTH_ERROR_NO_MEMORY;
	}

	memcpy(
		nth_plugin->plugin_name,
		plugin_name,
		sizeof(plugin_name)
	);

	nth_plugin->plugin_description = malloc(sizeof(plugin_desc));

	if (!nth_plugin->plugin_description){
		free(nth_plugin->plugin_name);
		nth_plugin->plugin_name = 0;
		return NTH_ERROR_NO_MEMORY;
	}

	memcpy(
		nth_plugin->plugin_description,
		plugin_desc,
		sizeof(plugin_desc)
	);

	nth_plugin->plugin_callback = Nth_Plugin_Callback;

	return 0;
}

int Nth_Plugin_Callback(
	struct Nth_Program * nth_program,
	struct Nth_Number * plugin_number
){
	int error = 0;

	size_t bin_diff = 0;
	size_t dec_diff = 0;
	size_t hex_diff = 0;
	size_t oct_diff = 0;

	char * numerical_base_str = 0;
	size_t numerical_base = 0;

	char * number_str = 0;

	size_t current_length = 0;
	size_t new_length = 0;

	__mpz_struct mpz_cast;

	if (!nth_program){
		return 1;
	}

	error = Get_Next_Nth_Argument(
		nth_program,
		&numerical_base_str
	);

	if (error){

		Push_Nth_Failure(
			nth_program,
			"Nth_Plugin_Callback",
			"Failed to get numerical base"
		);

		return 1;
	}

	bin_diff = strcmp("bin", numerical_base_str);
	dec_diff = strcmp("dec", numerical_base_str);
	hex_diff = strcmp("hex", numerical_base_str);
	oct_diff = strcmp("oct", numerical_base_str);

	if (bin_diff == 0){

		numerical_base = 2;

	} else if (dec_diff == 0){

		numerical_base = 10;

	} else if (hex_diff == 0){

		numerical_base = 16;

	} else if (oct_diff == 0){

		numerical_base = 8;

	} else {

		Push_Nth_Failure(
			nth_program,
			"Nth_Plugin_Callback",
			"Unrecognized numerical base"
		);

		return 1;
	}

	error = Iterate_Nth_Argument(nth_program);

	if (error){

		Push_Nth_Failure(
			nth_program,
			"Nth_Plugin_Callback",
			"Failed to iterate numerical base argument"
		);

		return 1;
	}

	error = Evaluate_Nth_Expression(
		nth_program,
		plugin_number
	);

	if (error){

		Push_Nth_Failure(
			nth_program,
			"Nth_Plugin_Callback",
			"Failed to get expression for viewing"
		);

		return 1;
	}

	if (!plugin_number->number_length){

		Push_Nth_Failure(
			nth_program,
			"Nth_Plugin_Callback",
			"Number length is zero"
		);

		return 1;
	}

	if (!plugin_number->number_buffer){

		Push_Nth_Failure(
			nth_program,
			"Nth_Plugin_Callback",
			"Number buffer is null"
		);

		return 1;
	}

	current_length = 0;
	current_length += plugin_number->number_length;

	if (current_length % sizeof(mp_ptr)){

		new_length = 0;
		new_length += sizeof(mp_ptr);
		new_length -= current_length % sizeof(mp_ptr);
		new_length += current_length;

		error = Set_Nth_Number_Length(
			plugin_number,
			new_length
		);

		if (error){

			Push_Nth_Failure(
				nth_program,
				"Nth_Plugin_Callback",
				"Failed to set new number length"
			);

			return 1;
		}
	}

	memset(&mpz_cast, 0, sizeof(mpz_cast));

	mpz_cast._mp_alloc = 0;
	mpz_cast._mp_alloc += plugin_number->number_length / sizeof(mp_ptr);

	mpz_cast._mp_size = 0;
	mpz_cast._mp_size += plugin_number->number_length / sizeof(mp_ptr);

	mpz_cast._mp_d = plugin_number->number_buffer;

	number_str = mpz_get_str(
		0,
		numerical_base,
		&mpz_cast
	);

	if (number_str && (number_str[0] == 0)){

		fprintf(stdout, "%s\n", "0");
		free(number_str);

	} else if (number_str && (number_str[0] >= 0)){

		fprintf(stdout, "%s\n", number_str);
		free(number_str);

	} else {

		Push_Nth_Failure(
			nth_program,
			"Nth_Plugin_Callback",
			"Failed to get number string"
		);

		return 1;
	}

	return 0;
}

